import cherrypy
from cherrypy import request, response
from cherrypy._cpdispatch import MethodDispatcher, LateParamPageHandler

class RestDispatcher(MethodDispatcher):
    """Additional dispatch based on RESTful URLs

    See http://microformats.org/wiki/rest/urls for details.

    Methods named GET, POST, etc will be called on an exposed class. The
    method names must be all caps; the appropriate Allow header will be output
    showing all capitalized method names as allowable HTTP verbs. Note that
    the containing class must be exposed, not the capitalized methods.

    class Phones(object):
        expose = True
        
        def GET(self, id="all", pid=None)
            return ""
            
        def new(self, entry):
            return ""
        new.exposed = True
            
    class People(object):
        expose = True
        
        phones = Phones()
        
        def GET(self, id="all"):
            return ""
            
        def edit(self, id):
            return ""
        edit.exposed = True

    URLs supported are:
    
    /people/
    /people/1
    /people/2/phones/
    /people/2/phones/4
        
    Note that additional exposed methods may exist within the REST resource to
    allow for URLs such as:
    
    /people/new or
    /people/1/edit
    /people/1/phones/new 
    /people/1/phones/2/edit
    """
    
    def __call__(self, path_info):
        """Set handler and config for the current request."""
        request = cherrypy.request
        
        resource, vpath = self.find_handler(path_info)
        
        # Extract additional methods and parameters from the vpath
        methods = [method for method in vpath if vpath.index(method) % 2]
        params = [param for param in vpath if not vpath.index(param) % 2]
                
        if methods:
            # Remove all params from path info. Thus /people/1/phones/2 will 
            # now become /people/phones/
            path_info = path_info.split('/')
            for param in params:
                path_info.remove(param)
            
            # Reverse param list, so that each REST resource gets it's own
            # id first, then the parent, grandparent etc. Thus the path_info
            # now becomes /people/phones/2/1
            params.reverse()
            path_info = '/'.join(path_info + params)
            
            # Need to decode new resource to check if resource is an exposed
            # method inside any of the child REST resources of the parent REST 
            # controller. This will allow us to find methods such as:
            # /people/2/phones/3/edit or /people/2/phones/new
            resource, vpath = self.find_handler(path_info)

        # Allow for exposed methods within a resource class for REST verbs:
        # /people/new, /people/3/phones/3/edit etc
        if resource and hasattr(resource, 'func_name'):
            # Decode any leftover %2F in the virtual_path atoms.
            vpath = [x.replace("%2F", "/") for x in vpath]
            request.handler = LateParamPageHandler(resource, *vpath)
        else:
            # Must be a REST resource class. Use Method dispatcher to set the
            # request.handler.
            
            # TODO: we could avoid another call to find_handler by duplicating
            # the code from MethodDispatcher here and subclass Dispatcher 
            # instead. Is it worth it?
            MethodDispatcher.__call__(self, path_info)
            